// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:build js&&wasm 
// +build js，wasm 

package runtime

import (
	_ "unsafe"
)

// /js/wasm还不支持线程。没有先发制人的权利。

const (
	mutex_unlocked = 0
	mutex_locked   = 1

	note_cleared = 0
	note_woken   = 1
	note_timeout = 2

	active_spin     = 4
	active_spin_cnt = 30
	passive_spin    = 1
)

func lock(l *mutex) {
	lockWithRank(l, getLockRank(l))
}

func lock2(l *mutex) {
	if l.key == mutex_locked {
		// js/wasm是单线程的，所以我们永远不应该遵守这一点。
		throw("self deadlock")
	}
	gp := getg()
	if gp.m.locks < 0 {
		throw("lock count")
	}
	gp.m.locks++
	l.key = mutex_locked
}

func unlock(l *mutex) {
	unlockWithRank(l)
}

func unlock2(l *mutex) {
	if l.key == mutex_unlocked {
		throw("unlock of unlocked lock")
	}
	gp := getg()
	gp.m.locks--
	if gp.m.locks < 0 {
		throw("lock count")
	}
	l.key = mutex_unlocked
}

// 一次性通知。

type noteWithTimeout struct {
	gp       *g
	deadline int64
}

var (
	notes            = make(map[*note]*g)
	notesWithTimeout = make(map[*note]noteWithTimeout)
)

func noteclear(n *note) {
	n.key = note_cleared
}

func notewakeup(n *note) {
	// gp:=getg（）
	if n.key == note_woken {
		throw("notewakeup - double wakeup")
	}
	cleared := n.key == note_cleared
	n.key = note_woken
	if cleared {
		goready(notes[n], 1)
	}
}

func notesleep(n *note) {
	throw("notesleep not supported by js")
}

func notetsleep(n *note, ns int64) bool {
	throw("notetsleep not supported by js")
	return false
}

// 与runtime·notetsleep相同，但在用户g（非g0）
func notetsleepg(n *note, ns int64) bool {
	gp := getg()
	if gp == gp.m.g0 {
		throw("notetsleepg on g0")
	}

	if ns >= 0 {
		deadline := nanotime() + ns
		delay := ns/1000000 + 1 // 上调用
		if delay > 1<<31-1 {
			delay = 1<<31 - 1 // 将上限设置为max int32 
		}

		id := scheduleTimeoutEvent(delay)
		mp := acquirem()
		notes[n] = gp
		notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline}
		releasem(mp)

		gopark(nil, nil, waitReasonSleep, traceEvNone, 1)

		clearTimeoutEvent(id) // 注意可能早醒了，清除超时
		clearIdleID()

		mp = acquirem()
		delete(notes, n)
		delete(notesWithTimeout, n)
		releasem(mp)

		return n.key == note_woken
	}

	for n.key != note_woken {
		mp := acquirem()
		notes[n] = gp
		releasem(mp)

		gopark(nil, nil, waitReasonZero, traceEvNone, 1)

		mp = acquirem()
		delete(notes, n)
		releasem(mp)
	}
	return true
}

// 签出超时将恢复等待已到达其截止日期的便笺的goroutines。
func checkTimeouts() {
	now := nanotime()
	for n, nt := range notesWithTimeout {
		if n.key == note_cleared && now >= nt.deadline {
			n.key = note_timeout
			goready(nt.gp, 1)
		}
	}
}

// 事件是从JavaScript到Go的调用堆栈。
var events []*event

type event struct {
	// g是JavaScript调用发生时的活动goroutine。
	// 返回JavaScript时需要处于活动状态。
	gp *g
	// 返回的报告事件处理程序是否已返回。
	// 当所有goroutine都空闲且事件处理程序返回时，
	// 然后g恢复并将执行返回到JavaScript。
	returned bool
}

// 由beforeIdle启动的超时事件。
var idleID int32

// beforeIdle在没有goroutine处于唤醒状态时被调度程序调用。
// 如果我们还没有处理事件，那么我们会暂停处理异步事件。
// 如果事件处理程序返回，我们将恢复它，它将暂停执行。
// beforeIdle将特定的goroutine返回到schedule next，或
// 使用otherReady指示某些goroutine已就绪。
func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) {
	delay := int64(-1)
	if pollUntil != 0 {
		delay = pollUntil - now
	}

	if delay > 0 {
		clearIdleID()
		if delay < 1e6 {
			delay = 1
		} else if delay < 1e15 {
			delay = delay / 1e6
		} else {
			// 定时器等待时间的任意上限。
			// 1e9毫秒==~11.5天。
			delay = 1e9
		}
		idleID = scheduleTimeoutEvent(delay)
	}

	if len(events) == 0 {
		go handleAsyncEvent()
		return nil, true
	}

	e := events[len(events)-1]
	if e.returned {
		return e.gp, false
	}
	return nil, false
}

func handleAsyncEvent() {
	pause(getcallersp() - 16)
}

// clearIdleID清除由beforeIdle启动的超时记录。
func clearIdleID() {
	if idleID != 0 {
		clearTimeoutEvent(idleID)
		idleID = 0
	}
}

// pause将SP设置为newsp，并暂停Go的WebAssembly代码的执行，直到触发事件。
func pause(newsp uintptr)

// scheduleTimeoutEvent告诉WebAssembly环境在毫秒后触发事件。
// 返回一个可与clearTimeoutEvent一起使用的计时器id。
func scheduleTimeoutEvent(ms int64) int32

// clearTimeoutEvent清除ScheduleTimoutEvent计划的超时事件。
func clearTimeoutEvent(id int32)

// handleEvent在从JavaScript调用Go时被调用。它调用syscall/js包
// 的事件处理程序，然后停止处理程序goroutine以允许其他goroutine在将执行返回给JavaScript之前运行。
// 当不再有其他goroutine处于唤醒状态时，在oreidle恢复处理程序goroutine之前。现在运行的goroutine 
// 与从JavaScript传入调用时运行的goroutine一样，执行可以安全地传递回JavaScript。
func handleEvent() {
	e := &event{
		gp:       getg(),
		returned: false,
	}
	events = append(events, e)

	eventHandler()

	clearIdleID()

	// 等待所有goroutine空闲
	e.returned = true
	gopark(nil, nil, waitReasonZero, traceEvNone, 1)

	events[len(events)-1] = nil
	events = events[:len(events)-1]

	// 将执行返回到JavaScript 
	pause(getcallersp() - 16)
}

var eventHandler func()

// go:linkname setEventHandler syscall/js.setEventHandler 
func setEventHandler(fn func()) {
	eventHandler = fn
}
